﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GenericCopier;
using Microsoft.Xna.Framework;
using UberChess.Structure.Move;
using UberChess.Structure.Pieces;
using UberChess.StateGenerator;

namespace UberChess
{
    [Serializable]
    public class State
    {
        public IPiece[,] state;
        public Stack<IPiece> capturedPieces { get; private set; }

        public State()
        {
            state = new IPiece[Constants.STATESQUAREWIDTH, Constants.STATESQUAREHEIGHT];
            capturedPieces = new Stack<IPiece>();
        }

        public void EmptyInitialize()
        {
            for (var i = 0; i < Constants.STATESQUAREWIDTH; i++)
                for (var j = 0; j < Constants.STATESQUAREHEIGHT; j++)
                {
                    state[i, j] = new Empty();
                }
        }

        public void Initialize()
        {
            for (var i = 0; i < Constants.STATESQUAREWIDTH; i++)
                for (var j = 0; j < Constants.STATESQUAREHEIGHT; j++ )
                {
                    state[i, j] = new Empty();
                }

            //Starting Positions
            state[3, 0] = new Queen(Side.BLACK, new Vector2(3, 0));
            state[4, 0] = new King(Side.BLACK, new Vector2(4, 0));
            state[0, 0] = new Rook(Side.BLACK, new Vector2(0, 0));
            state[7, 0] = new Rook(Side.BLACK, new Vector2(7, 0));
            state[1, 0] = new Knight(Side.BLACK, new Vector2(1, 0));
            state[6, 0] = new Knight(Side.BLACK, new Vector2(6, 0));
            state[2, 0] = new Bishop(Side.BLACK, new Vector2(2, 0));
            state[5, 0] = new Bishop(Side.BLACK, new Vector2(5, 0));
            state[0, 1] = new Pawn(Side.BLACK, new Vector2(0, 1));
            state[1, 1] = new Pawn(Side.BLACK, new Vector2(1, 1));
            state[2, 1] = new Pawn(Side.BLACK, new Vector2(2, 1));
            state[3, 1] = new Pawn(Side.BLACK, new Vector2(3, 1));
            state[4, 1] = new Pawn(Side.BLACK, new Vector2(4, 1));
            state[5, 1] = new Pawn(Side.BLACK, new Vector2(5, 1));
            state[6, 1] = new Pawn(Side.BLACK, new Vector2(6, 1));
            state[7, 1] = new Pawn(Side.BLACK, new Vector2(7, 1));

            state[3, 7] = new Queen(Side.WHITE, new Vector2(3, 7));
            state[4, 7] = new King(Side.WHITE, new Vector2(4, 7));
            state[0, 7] = new Rook(Side.WHITE, new Vector2(0, 7));
            state[7, 7] = new Rook(Side.WHITE, new Vector2(7, 7));
            state[1, 7] = new Knight(Side.WHITE, new Vector2(1, 7));
            state[6, 7] = new Knight(Side.WHITE, new Vector2(6, 7));
            state[2, 7] = new Bishop(Side.WHITE, new Vector2(2, 7));
            state[5, 7] = new Bishop(Side.WHITE, new Vector2(5, 7));
            state[0, 6] = new Pawn(Side.WHITE, new Vector2(0, 6));
            state[1, 6] = new Pawn(Side.WHITE, new Vector2(1, 6));
            state[2, 6] = new Pawn(Side.WHITE, new Vector2(2, 6));
            state[3, 6] = new Pawn(Side.WHITE, new Vector2(3, 6));
            state[4, 6] = new Pawn(Side.WHITE, new Vector2(4, 6));
            state[5, 6] = new Pawn(Side.WHITE, new Vector2(5, 6));
            state[6, 6] = new Pawn(Side.WHITE, new Vector2(6, 6));
            state[7, 6] = new Pawn(Side.WHITE, new Vector2(7, 6));

            //state[4, 0] = new King(Side.BLACK, new Vector2(4, 0));
            //state[2, 0] = new Bishop(Side.BLACK, new Vector2(2, 0));
            //state[5, 0] = new Bishop(Side.BLACK, new Vector2(5, 0));
            //state[3, 3] = new Rook(Side.WHITE, new Vector2(3, 3));
            //state[5, 3] = new Rook(Side.WHITE, new Vector2(5, 3));
            //state[3, 7] = new Queen(Side.WHITE, new Vector2(3, 7));
            //state[7, 7] = new King(Side.WHITE, new Vector2(7, 7));

            //state[5, 1] = new Pawn(Side.BLACK, new Vector2(5, 1));
            //state[4, 2] = new King(Side.BLACK, new Vector2(4, 2));
            //state[5, 2] = new Knight(Side.BLACK, new Vector2(5, 2));
            //state[4, 3] = new Pawn(Side.BLACK, new Vector2(4, 3));
            //state[4, 4] = new Pawn(Side.WHITE, new Vector2(4, 4));
            //state[5, 5] = new Pawn(Side.WHITE, new Vector2(5, 5));
            //state[0, 5] = new Bishop(Side.WHITE, new Vector2(0, 5));
            //state[4, 6] = new Bishop(Side.WHITE, new Vector2(4, 6));
            //state[2, 5] = new Pawn(Side.WHITE, new Vector2(2, 5));
            //state[3, 7] = new Queen(Side.WHITE, new Vector2(3, 7));
            //state[4, 7] = new King(Side.WHITE, new Vector2(4, 7));
            //state[7, 7] = new Rook(Side.WHITE, new Vector2(7, 7));
        }

        public IPiece GetPieceAtPosition(Vector2 position)
        {
            return state[(int)position.X, (int)position.Y];
        }

        public void SetPieceAtPosition(Vector2 position, IPiece piece)
        {
            if(piece.GetType() != typeof(Empty)) piece.CurrentPosition = position;
            state[(int) position.X, (int) position.Y] = piece;
        }

        public bool IsWithinBounds(Vector2 position)
        {
            var length = Constants.STATESQUAREWIDTH > position.X && position.X >= 0;
            var height = Constants.STATESQUAREHEIGHT > position.Y && position.Y >= 0;
            return length && height;
        }

        public void AddPieceToCapturedList(IPiece piece)
        {
            capturedPieces.Push(piece);
        }

        public State CastlingMove(Vector2 selected, Vector2 position)
        {
            var king = GetPieceAtPosition(selected);
            var kingY = king.CurrentPosition.Y;
            if (position.X > king.CurrentPosition.X)
            {
                var newState = MovePiece(selected, position);
                return newState.MovePiece(new Vector2(7, kingY), new Vector2(5, kingY));
            }
            else
            {
                var newState = MovePiece(selected, position);
                return newState.MovePiece(new Vector2(0, kingY), new Vector2(3, kingY));
            }
        }

        public State CastlingMove(PossibleMove possibleMove)
        {
            return CastlingMove(possibleMove.currentPosition, possibleMove.possibleMove);
        }

        public State MovePiece(Vector2 selected, Vector2 position)
        {
            var newState = (State)Copier.Copy(this);
            newState.state[(int)position.X, (int)position.Y] = GetPieceAtPosition(selected);
            newState.GetPieceAtPosition(position).CurrentPosition = (position);
            newState.state[(int) selected.X, (int) selected.Y] = new Empty();

            return newState;
        }

        public State MovePiece(Vector2 selected, Vector2 position, ExchangeType exchangeType)
        {
            var newState = MovePiece(selected, position);
            var pieceAtPosition = newState.GetPieceAtPosition(position);
            if (pieceAtPosition.GetType() != typeof(Empty))
            {
                var side = pieceAtPosition.GetSide();
                if (newState.IsExchangePossible(side))
                {
                    return newState.Exchange(exchangeType, side);
                }
            }
            return newState;
        }

        public State MovePiece(PossibleMove possibleMove, ExchangeType exchangeType)
        {
            return MovePiece(possibleMove.currentPosition, possibleMove.possibleMove, exchangeType);
        }

        public State CapturePiece(PossibleMove possibleMove, ExchangeType exchangeType)
        {
            return CapturePiece(possibleMove.currentPosition, possibleMove.possibleMove, exchangeType);
        }

        public State CapturePiece(Vector2 selected, Vector2 position)
        {
            var newState = (State)Copier.Copy(this);
            newState.AddPieceToCapturedList(newState.GetPieceAtPosition(position));
            newState.SetPieceAtPosition(position, newState.GetPieceAtPosition(selected));
            newState.GetPieceAtPosition(position).CurrentPosition = (position);
            newState.SetPieceAtPosition(selected, new Empty());
            return newState;
        }

        public State CapturePiece(Vector2 selected, Vector2 position, ExchangeType exchangeType)
        {
            var newState = CapturePiece(selected, position);

            var pieceAtPosition = newState.GetPieceAtPosition(position);
            if(pieceAtPosition.GetType() != typeof(Empty))
            {
                var side = pieceAtPosition.GetSide();
                if (newState.IsExchangePossible(side))
                {
                    return newState.Exchange(exchangeType, side);
                }
            }

            return newState;
        }


        public bool ContainsKing(Side side)
        {
            foreach (var p in state)
            {
                if (p.GetType() == typeof(King) && p.GetSide() == side)
                {
                        return true;
                }
            }
            return false;
        }

        public List<IPiece> GetPieces()
        {
            var pieces = new List<IPiece>();
            foreach (var piece in state)
            {
                if (piece.GetType() != typeof(Empty))
                    pieces.Add(piece);
            }
            return pieces;
        }

        public bool CheckForCheckMate(Side side)
        {
            var pieces = GetPieces();
            var king = pieces.Single(p => p.GetSide() == side && p.GetType() == typeof (King));
            var kingPosition = king.CurrentPosition;
            var threateningPieces = pieces.Where(piece => piece.GetSide() != side && piece.GetPossibleCaptureMoves(this).Any(move => move.isCaptureMove && move.possibleMove == kingPosition)).ToList();

            if (threateningPieces.Count() == 0)
                return false;
            
            if(threateningPieces.Count() == 1)
            {
                var ownPieces = pieces.Where(piece => piece.GetSide() == side).ToList();
                if(ownPieces.Any(piece => piece.GetPossibleCaptureMoves(this)
                    .Any(move => move.possibleMove == threateningPieces.First().CurrentPosition == move.isCaptureMove) && piece.GetType() != typeof(King)))
                {
                    return false;
                }

                

                var ownMoves = new List<PossibleMove>();
                ownPieces.Where(piece => piece.GetType() != typeof(King)).ToList().ForEach(piece => ownMoves.AddRange(piece.GetPossibleMoves(this)));
                foreach (var threateningPathMove in threateningPieces.First().GetPossibleMoves(this))
                {
                    if(ownMoves.Any(move => move.possibleMove == threateningPathMove.possibleMove && !threateningPathMove.isCaptureMove))
                        return false;
                }
            }

            var opponentPieces = pieces.Where(piece => piece.GetSide() != side).ToList();
            var opponentPiecesPossibleMoves = new List<PossibleMove>();
            opponentPieces.ForEach(piece => opponentPiecesPossibleMoves.AddRange((piece.GetType() == typeof(Pawn) ? piece.GetPossibleCaptureMoves(this) : piece.GetPossibleMoves(this))));
            foreach (var possibleMove in king.GetPossibleMoves(this))
            {
                if (!opponentPiecesPossibleMoves.Any(move => move.possibleMove == possibleMove.possibleMove))
                    return false;
            }

            return true;
        }

        public bool CheckForCheck(Side side)
        {
            var pieces = GetPieces();
            var king = pieces.Single(p => p.GetSide() == side && p.GetType() == typeof(King));
            var kingCurrentPosition = king.CurrentPosition;
            var opponentPieces = pieces.Where(p => p.GetSide() != side && p.GetType() != typeof(King));

            return opponentPieces.Select(piece => piece.GetPossibleMoves(this)).Any(moves => moves.Any(m => m.possibleMove == kingCurrentPosition && m.isCaptureMove));
        }

        public bool IsCastlingPossible(Side side)
        {
            var pieces = GetPieces();
            var king = pieces.Single(p => p.GetSide() == side && p.GetType() == typeof(King));

            if (((King)king).hasMoved)
                return false;

            if (IsBlocked(king))
                return false;

            if (CheckForCheck(side))
                return false;

            return IsShortCastlingPossible(side, pieces) || IsLongCastlingPossible(side, pieces);
        }

        private bool IsBlocked(IPiece king)
        {
            var kingY = (int)king.CurrentPosition.Y;

            if (state[5, kingY].GetType() == typeof(Empty) && state[6, kingY].GetType() == typeof(Empty)
                || (state[3, kingY].GetType() == typeof(Empty) && state[2, kingY].GetType() == typeof(Empty) && state[1, kingY].GetType() == typeof(Empty)))
                return false;

            return true;
        }

        public bool IsShortCastlingPossible(Side side, List<IPiece> pieces)
        {
            var king = pieces.Single(p => p.GetSide() == side && p.GetType() == typeof(King));

            var rightRook = pieces.Where(p => p.GetSide() == side && p.GetType() == typeof(Rook) &&
                p.CurrentPosition.X > king.CurrentPosition.X);
            
            var kingY = (int)king.CurrentPosition.Y;

            if (!(state[5, kingY].GetType() == typeof(Empty) && state[6, kingY].GetType() == typeof(Empty)))
                return false;

            if (rightRook.Count() > 0)
                if (!((Rook)rightRook.First()).hasMoved)
                    return true;

            return false;
        }

        public bool IsLongCastlingPossible(Side side, List<IPiece> pieces)
        {
            var king = pieces.Single(p => p.GetSide() == side && p.GetType() == typeof(King));

            var leftRook = pieces.Where(p => p.GetSide() == side && p.GetType() == typeof(Rook) &&
                    p.CurrentPosition.X < king.CurrentPosition.X);

            var kingY = (int)king.CurrentPosition.Y;

            if (!(state[1, kingY].GetType() == typeof(Empty) &&
                state[2, kingY].GetType() == typeof(Empty) &&
                state[3, kingY].GetType() == typeof(Empty)))
                return false;

            if (leftRook.Count() > 0)
                if (!((Rook)leftRook.First()).hasMoved)
                    return true;

            return false;
        }

        public bool CheckForDraw()
        {
            return false;
        }

        public bool IsExchangePossible(Side side)
        {
            var pieces = GetPieces();
            return pieces.Any(p => p.CurrentPosition.Y == (side == Side.WHITE ? 0 : 7) && p.GetType() == typeof(Pawn));
        }

        public State Exchange(ExchangeType exchangeSelection, Side side)
        {
            var pieces = GetPieces();
            var pieceToBeExchanged = pieces.Where(p => p.CurrentPosition.Y == (side == Side.WHITE ? 0 : 7) && p.GetType() == typeof(Pawn)).ToList();
            var newState = (State) Copier.Copy(this);
            pieceToBeExchanged.ForEach(p => newState.state[(int)p.CurrentPosition.X, (int)p.CurrentPosition.Y] = ExchangePiece(exchangeSelection, side, p.CurrentPosition));

            return newState;
        }

        private IPiece ExchangePiece(ExchangeType exhangeSelection, Side side, Vector2 position)
        {
            switch (exhangeSelection)
            {
                case ExchangeType.Queen:
                    return new Queen(side, position);
                case ExchangeType.Rook:
                    return new Rook(side, position);
                case ExchangeType.Bishop:
                    return new Bishop(side, position);
                case ExchangeType.Knight:
                    return new Knight(side, position);
            }

            throw new Exception("Couldn't exchange piece!");
        }

       public int MaterialValue(Side side)
       {
          return this.GetPieces().Where(x => x.GetSide() == side).Sum(x => x.GetMaterialValue());
       }
    }
}
